<template>
  <div class="upload-file-wrapper">
    <input type="file" @change="uploadFile" />
    <el-button>上传文件</el-button>
    <div class="box" :style="{ left: left + 'px' }"></div>
  </div>
</template>

<script lang="ts">
const chunkSize = 0.5 * 1024 * 1024;
type formDatasType = {
  formData: FormData; // 切片上传请求的参数
  index: number; // 当前切片是第几个
  error: number; // 当前切片上传错误次数
  progress: number; // 当前切片上传进度
};
export default {
  data() {
    return {
      left: 0,
    };
  },
  mounted() {
    setInterval(() => {
      this.left = this.left + 5;
    }, 1000);
  },
  methods: {
    async uploadFile(e: any) {
      const chunks = this.createFileChunk(e.target.files[0]);
      const fileHash: string = await this.calculationChunksMd5(chunks);
      console.log("是否已上传？ 是否已上传部分? 如果上传了部分，那么上传了那部分");
      const postFormData = this.createPostFormData(chunks, fileHash);
      await this.postToServer(postFormData);
      console.log("将hash和文件后缀发送给后端，让其合并文件");
    },

    /**
     * 将文件上传服务器
     */
    postToServer(postFormData: Array<formDatasType>, limt: number = 3) {
      return new Promise((resolve) => {
        let len = postFormData.length;
        let counter = 0;
        let isStop = false;
        const startPost: Function = async () => {
          // 注意这个方法会改变原数组
          const formDatas = postFormData.shift();
          if (!formDatas || isStop) return;
          try {
            await this.$http.post("/uploadfile", formDatas.formData, {
              onUploadProgress: (progress: any) => {
                // 这里可以获取进度： Number(((progress.loaded / progress.total) * 100).toFixed(2));
              },
            });

            // 所有请求都已结束，我们需要结束外面的Promise
            if (counter == len - 1) {
              resolve(true);
              return;
            }

            counter++;
            // 请求还未结束,继续启动任务
            startPost();
          } catch (error) {
            // 超过三次就放弃了
            if (formDatas.error > 3) {
              return (isStop = true);
            }
            // 将错误的内容放入数据列表中，然后立马进行重试
            formDatas.error++;
            postFormData.unshift(formDatas);
            // 继续启动任务
            startPost();
          }
        };

        // 初始启动limt个任务
        for (let index = 0; index < limt; index++) {
          startPost();
        }
      });
    },

    /**
     * 创建请求参数数组
     */
    createPostFormData(
      chunks: Array<Blob>,
      fileHash: string
    ): Array<formDatasType> {
      const formDatas = chunks.map((chunk, index) => {
        // 请求的参数创建
        const formData = new FormData();
        formData.append("chunk", chunk);
        formData.append("hash", fileHash);
        formData.append("fileName", fileHash + "-" + index);
        // 添加额外参数，可以做错误重试，进度等操作
        return { formData, index, error: 0, progress: 0 };
      });

      return formDatas;
    },
    /**
     * file: 文件上传时，通过e.target.files[0]拿到的值
     */
    createFileChunk(file: File) {
      let current = 0;
      let chunks: Array<Blob> = [];
      while (current < file.size) {
        let chunk = file.slice(current, current + chunkSize);
        chunks.push(chunk);
        current = current + chunkSize;
      }
      return chunks;
    },
    /**
     * 计算md5值
     */
    calculationChunksMd5(chunks: Array<Blob>): Promise<string> {
      return new Promise((resolve) => {
        const worker = new Worker("/hashMd5.js");
        worker.postMessage(chunks);
        worker.onmessage = (e: MessageEvent<any>) => {
          resolve(e.data);
        };
      });
    },
  },
};
</script>
<style>
.box {
  position: absolute;
  top: 400px;
  width: 10px;
  height: 10px;
  background: red;
}
</style>
